package testcl;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashMap;

/**
 * Class loader for the TComponent extensions. All classes must implement TComponent.
 *  and must pe put inside the <code>classDir</code> of the constructor
 *  (no other subdirs allowed).
 * @author Bogdan Moza
 */
public class CustomClassLoader extends ClassLoader
{
    private HashMap<String, Class> classes = new HashMap<String, Class>();
    private String classDirectory;
    private String packageName;

    /**
     * Creates a new custom class loader. Required to specify the directory in
     * which to search for classes.
     * 
     * @param   classDir    Path to class directory. Must have <b>/</b> at end.
     */
    public CustomClassLoader(String classDir, String pkgName) {
        classDirectory = classDir + (classDir.endsWith("/") ? "" : "/");
        packageName = pkgName;

        System.out.println("CCL initialized with class dir:'" + classDirectory + "' and package:'" + packageName + "'");
    }

    /**
     * Loads <code>className</code> from the <code>classDir</code>. Only classes
     *  with package <code>pkgName</code> specified in constructor are allowed.<br />
     * This method is same with <code>loadClass(className, true)</code>
     *
     * @param   className   Fully qualified classname. Only <code>pgkName</code> classes are loaded from files.
     * @return  The loaded class.
     * @throws  ClassNotFoundException
     */
    @Override
    public synchronized Class loadClass(String className)
            throws ClassNotFoundException {
        return loadClass(className, true);
    }

    /**
     * Loads <code>className</code> from the <code>classDir</code>. Only classes
     *  with package <code>pkgName</code> specified in constructor are allowed.
     *
     * @param   className   Fully qualified classname. Only <code>pgkName</code> classes are loaded from files.
     * @param   resolveIt   Specifies if the classloader should resolve all the dependend classes also.
     * @return  The loaded class.
     * @throws  ClassNotFoundException
     */
    @Override
    public synchronized Class loadClass(String className, boolean resolveIt)
            throws ClassNotFoundException {

        Class result;
        byte[] classData;

        //check to see if I allready loaded this class
        result = classes.get(className);
        if (result != null) {
            return result;
        }

        //check with the primordial class loader
        try {
            result = super.findSystemClass(className);
            return result;
        }
        catch (ClassNotFoundException ex) {
            //class was not found by primordial class loader

            // check that it is in the allowed package
            if (!className.startsWith(packageName))
                throw new ClassNotFoundException("Class not allowed to be loaded. Not in " + packageName + " package");
        }
        //load the class bytes
        classData = loadClassData(className);

        //define de the class
        result = defineClass(className, classData, 0, classData.length);

        //resove it
        if (resolveIt) {
            resolveClass(result);
        }

        //store it in out hashmap
        classes.put(className, result);

        return result;
    }

    private byte[] loadClassData(String className) throws ClassNotFoundException {
        try {
            FileInputStream in = new FileInputStream(classDirectory + className.replace(packageName+".", "").concat(".class"));
            byte[] classData = new byte[in.available()];
            in.read(classData);

            return classData;

        }
        catch (IOException ex) {
            throw new ClassNotFoundException(ex.getMessage());
        }
    }
}
